#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>

#define _POSIX_C_SOURCE 200809L
#define MAX_BUF 128
#define chop(str) str[strlen(str)-1] = 0x00;

#include "script_parser.h"
#include "commands.h"
#include "ready_queue.h"
#include "util.h"


static script_dict script_mode[] = {
	{ "W", "", 0, 0, 0, script_write},
	{ "R", "", 0, 0, 0, script_read },
	{ "D", "", 0, 0, 0, script_device }
};

static task_rq rq;
static void write_mmap(char *, size_t, const script_dict *);
static void create_task(script_dict*);

void
script_loader()
{
	const int buf_size = 1024;
	const char *file_path = "./script";
    script_dict *script;
    long fp_location;

	int line_num = 0;
	char buf[buf_size];
	FILE *fp;

    script = (script_dict *) malloc( sizeof(script_dict) );

	if( fp = fopen( file_path , "r" ) ) {
    
        while( fgets(buf, buf_size, fp) ) {
            parse_script( script, buf, line_num++ );
        }
	}
    create_task( script );
	fclose(fp);
}

static void
create_task(script_dict *script)
{
    char *file_name;
    pcb *new_task;
    pid_t pid, child_pid;
    
    pid = fork();

    switch(pid)
    {
        case -1 :
            perror("fork error ");
            break;
        case 0 : 
            child_pid = getpid();
           
            sprintf( file_name, "%ld", (long)child_pid );
            execl( "./up", file_name , NULL) ;


            
            break;
        default :

            // rq 에 자식 프로세스의 pcb를 넣어줌
            new_task = (pcb *)malloc( sizeof(pcb) );
            new_task->pid = pid;
            new_task->priority = script->priority;
            new_task->tsc = script->tsc;
            

            sprintf( file_name, "%ld", (long)pid );
            write_mmap( file_name, sizeof(script_dict), script );
            
            /*write_mmap( convert_int_str( pid ), sizeof(script_dict), script );*/
            sleep(5);
            break;
    }

    file_name = NULL;
}


static void
write_mmap( char *file_name, size_t mem_size, const script_dict *script )
{
    int fd;
    script_dict *script_map;
    int i = 0;

    fd = open( file_name, O_RDWR | O_CREAT, 0666);

    if( fd < 0 ) {


    }

    ftruncate( fd, mem_size );

    script_map = (script_dict *)mmap( 0, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );

    script_map[0] = *script;

    munmap(script_map, mem_size);

    close(fd);
}

static void
parse_script( script_dict *script, const char *script_line , int line_num )
{
	int i;
	char *script_param;

	script_param = (char *) malloc( strlen(script_line) + 1);
	strcpy( script_param, script_line );

	if( line_num == 0 ) {
		sscanf( script_param, "%d:%d" , &(script->tsc), &(script->priority) );
    }

	else {

		char *script_elem = strtok( script_param , ":" );
		script_elem = strtok( NULL, ":" );

		strcpy( script->script_elem, script_elem );

		char *script_val = strtok( NULL, " " );
		strcpy( script->script_val, script_val );

		for( i = 0; i < sizeof(script_mode) / sizeof(script); ++ i) {

			if( strcmp( script_mode[i].script_elem, script_elem ) == 0 ) {
    			// 해당 스크립 모드의 함수값을 포인터에 할당해줌
				script->script_exec = script_mode[i].script_exec;
				script->script_exec( script->script_val );
			}
		}	
	}

	free(script_param);
	script_param = NULL;
}

static void 
script_write( void *param )
{
	char *value = (char *)param;
	char *str_mem_addr = strtok( value, ":" );
	char *str_value = strtok( NULL, " " );

	int mem_addr = atoi(str_mem_addr);
	int mem_val = atoi(str_value);


}

static void 
script_read( void *param )
{
	char *value = (char *)param;
	char *str_mem_addr = strtok( value, ":" );
	char *str_register = strtok( NULL, " " );

	int mem_addr = atoi(str_mem_addr);
	int registers = atoi(str_register);

}

static void 
script_device( void *param )
{
	char *value = (char *)param;
	char *str_device_num = strtok( value, " " );
	int device_num = atoi(str_device_num);

}


static const COMMAND cmd_list[] = 
{
	{ CMD_MODE , cmd_mode },
	{ CMD_PASS , cmd_pass },
	{ CMD_KILL , cmd_kill },
	{ CMD_RUN  , cmd_run },
	{ CMD_SHOW , cmd_show },
	{ CMD_EXIT , cmd_exit }
};


void
execute_command( char *command )
{     
	int i, command_length;
	char *cmd_name, *cmd_param;

	command_length = strlen(command) + 1; 

	cmd_name = (char *) malloc( command_length );
	cmd_param = (char *) malloc( command_length );

	// 공백 기준으로 커맨드와 매개변수를 나눔   
	sscanf( command, "%s %s", cmd_name, cmd_param );

	for ( i = 0; i < sizeof( cmd_list ) / sizeof( COMMAND ); ++i ) {
		// 입력받은 명령어와 일치하는 명령어가 있을 경우   
		if( strcmp( cmd_list[i].cmd_name , cmd_name ) == 0 ) {
			// 매개변수가 있는 경우
			if( strlen( cmd_param ) != 0)
				cmd_list[i].cmd_exec( cmd_param );

			cmd_list[i].cmd_exec( '\0' );
		}
	}


	free(cmd_name);
	cmd_name = NULL;

	free(cmd_param);
	cmd_param = NULL;
}

static void 
cmd_mode( const void *cmd_param )
{
	int run_mode = atoi( (char *)cmd_param );

	switch(run_mode) {
		case FCFS : break;
		case SJF : break;
		case RR : break;
	}

}

static void 
cmd_pass( const void *cmd_param )
{
    int tsc = atoi( (char *)cmd_param );

    

}

static void 
cmd_run()
{

}

static void 
cmd_kill( const void *cmd_param )
{
	int pid;
	pid = atoi ( (char *)cmd_param );

	if (pid >0 )
	{
		kill( pid, SIGKILL );
		printf("시그널을 KILL합니다!!");

	}
	else
	{
		perror("잘못된 시그널/pid을(를) 보냈음!");
	}

}

/*
    FCFS rq의 순서,
    SJF next CPU burst 값,
    RR 우선순위
*/
static void 
cmd_show( task_rq *rq, int mode )
{
    pcb *task;
    
    do{
        
        task = list_entry( task->list.next, pcb, list );

        switch(mode)
        {
            case FCFS : break;
            case SJF : break;
            case RR : break;

        }

        
    } while( task->list.next != NULL );
    

}

static void 
cmd_exit() 
{
	int status;

	if( wait(&status) == -1 )
		return;

	exit(0);
}


void 
enqueue_task( task_rq *rq, pcb* task )
{
	if( is_empty(rq) ) { 

		rq->front = rq->rear = task; 
		//가장 앞에 잇으므로 prev는 Null이 된다.
		task->list.prev = NULL;

	} else {

		//rear의 next에 새로운 task를 입력
		rq->rear->list.next = &(task->list);
		//이전 rear에 있던 rear를 새로운 task의 prev에 둔다.
		task->list.prev = &(rq->rear->list);
		//현재 rear는 task이다
		rq->rear = task;
	}

	task->list.next = NULL;

	++rq->size;
}

void
dequeue_task( task_rq *rq )
{
	pcb *front_task;
    pid_t pid;
    pid = front_task->pid;

    pid = rq->front->pid;

    //pcb *front_task = rq->front;
	node *next_front_task = rq->front->list.next;

	if( !is_empty(rq) ) {

		if( rq->front->pid == rq->rear->pid ) {
			rq->front->pid = rq->rear->pid = 0;// NULL;
		} else {

			// 맨 앞을 비운다.	
			rq->front->list.next->prev = NULL;

			// 기존 front의 next에 있던 task를 front가 가리킨다			
			rq->front->pid = list_entry( next_front_task, pcb, list );
		}      

		--rq->size;
	}

	//return front_task;
}

int
is_empty( task_rq *rq ) 
{
	return rq->size == 0;
}

int 
get_queue_size( task_rq *rq )
{
	return rq->size;
}

pcb *
get_first_task( task_rq *rq )
{
	return rq->front;
}

pcb *
get_last_task( task_rq *rq )
{
	return rq->rear;
}

task_rq *
init_task_queue()
{
	task_rq *task_queue = (task_rq *) malloc ( sizeof(task_rq) );	

	task_queue->front = task_queue->rear = NULL;
	task_queue->size = 0;

	return task_queue;
}

void
clear_queue( task_rq *rq )
{
	while( !is_empty( rq ) ) {
		dequeue_task( rq );
	}
}


void
update_task( pid_t pid, pcb task )
{

}

void main() 
{
	char buf[MAX_BUF];	


    while(1) {
		
		// script를 읽어들이고
		script_loader();

		// 명령어를 입력받고 수행함
		memset( buf, 0x00, MAX_BUF );
		fgets( buf, MAX_BUF - 1, stdin );
		chop(buf);
		execute_command(buf);	

   }
}
